﻿using System;
using System.Text;
using System.IO;
using System.Net;
using PushEmail.Mime;
using System.Collections.Generic;
using PushEmail.Common;
using PushEmail.Common.Logging;
using PushEmail.Tools;
using PushEmail.Storage;
using System.Linq;

namespace MyMailWp8.Protocol
{
    public enum CMSmtpCommandState
    {
        CMSmtp_FROM = 0,
        CMSmtp_RCPTTO,
        CMSmtp_DATA,
        CMSmtp_EFWD,
        CMSmtp_MAILEND,
        CMSmtp_QUIT
    };

    internal class CMSmtpSend : CMPopBase
    {
        const int mail_sending = 1;     //邮件正在发送
        const int mail_sent = 2;        //发送完毕
        const int mail_sentfailed = 0;        //发送失败


        private CMSmtpCommandState _sendState = CMSmtpCommandState.CMSmtp_FROM;  //0-waiting for response of MAIL FROM ; 
        //1-waiting for response of RCPT TO
        //2-waiting for response of DATA
        private String _stringRawDatabuf;
        public List<String> _stringSendTo;
        internal Email sendMail;
        
        
        public CMSmtpSend(AsyncSocketClient sockClient,List<Email> list)
            : base(sockClient)
        {
            _stringSendTo = new List<string>();
            getSendMail();
        }

        public bool sendOneMail()
        {
            if(sendMail == null)
                return false;
            _stringRawDatabuf = "";
            addSendRCPTTOAddress(sendMail.toAddress);
            addSendRCPTTOAddress(sendMail.ccAddresses);
            addSendRCPTTOAddress(sendMail.bccAddresses);

            _sendState = CMSmtpCommandState.CMSmtp_FROM;
            

            string fromAddress = sendMail.fromAddress;
            Account account = PEDBManager.Instance().peDataModel.GetAccountByAddress(fromAddress);
            if(account != null)
            {
                if((account.accountType == (int)AccountTypeEnum.AccountType139) || (account.accountType == (int)AccountTypeEnum.AccountTypeEnterprise))
                    fromAddress = account.emailAddress;
                else //互联网邮箱
                {
                    account = PEDBManager.Instance().peDataModel.Get139Account();
                if (account != null)
                    fromAddress = account.emailAddress;
                else
                    return false;   //??? maybe next mail
                }
            }

            SendData("MAIL FROM:<" + fromAddress + ">" + "\r\n");
            return true;
        }

        private bool getSendMail()
        {
            try
            {
                sendMail = null;
                //uint folderId = PEDBManager.Instance().peDataModel.GetSystemFolderIdByType(account.accountId, FolderType.outBoxType);
                //List<Email> sendMailList = PEDBManager.Instance().peDataModel.GetMailsByFolderId(account.accountId, folderId);
                List<Email> sendMailList = PEDBManager.Instance().peDataModel.GetMailsByFolderType(FolderType.outBoxType);
                sendMailList = sendMailList.OrderByDescending(p => p.mimeDate).ToList();
                if ((sendMailList == null) || (sendMailList.Count == 0))
                    return false;

                sendMail = sendMailList[0];
                if (sendMail != null)
                {
                    sendMail.sendResult = mail_sending;
                    PEDBManager.Instance().peDataModel.UpdateMail(sendMail);
                }
                return true;

            }
            catch(Exception e)
            {
                return false;
            }
        }

        private void addSendRCPTTOAddress(string addresses)
        {
            try
            {
                List<EmailAddress> list = PushEmail.Tools.EmailAddress.GetEmailAddresses(addresses);
                foreach (EmailAddress emailAddr in list)
                {
                    _stringSendTo.Add(emailAddr.emailAddress);
                }
            }
            catch (Exception e)
            {
                DefaultLogger.Log.LogDebug(e.ToString());
            }
        }

        private string getContactEncode(string addresses)
        {
            string mime = null;
            try
            {
                List<EmailAddress> list = PushEmail.Tools.EmailAddress.GetEmailAddresses(addresses);
                foreach (EmailAddress emailAddr in list)
                {
                    if (string.IsNullOrEmpty(emailAddr.aliasName))
                    {
                        mime += emailAddr.emailAddress + ",";
                    }
                    else
                    {
                        mime += "=?UTF-8?B?" + Convert.ToBase64String(Encoding.UTF8.GetBytes(emailAddr.aliasName)) + "?=";
                        mime += "<" + emailAddr.emailAddress + ">,";
                    }
                }
            }
            catch (Exception e)
            {
                DefaultLogger.Log.LogDebug(e.ToString());
            }
            if (!string.IsNullOrEmpty(mime))
            {
                mime = mime.TrimEnd(',');
            }
            return mime;
        }

        private string getMimedata(Email sendMail)
        {
            string strMailContent = IsolateStorageUtil.ReadEmailContentFile(sendMail);
            byte[] data = null;
            if (strMailContent != null)
                data = Encoding.UTF8.GetBytes(strMailContent);

            StringBuilder stringBuilder = new StringBuilder();

            
            string mimeData = null;

            stringBuilder.Append("Date: " + sendMail.mimeDate + "\r\n");
            Account account = PEDBManager.Instance().peDataModel.GetAccountByAddress(sendMail.fromAddress);
            if ((account == null) || (string.IsNullOrEmpty(account.alias)))
                stringBuilder.Append("From: " + getContactEncode(sendMail.fromAddress) + "\r\n");
            else
                stringBuilder.Append("From: " + getContactEncode("\"" + account.alias + "\"<" + account.emailAddress + ">") + "\r\n");

            if (string.IsNullOrEmpty(sendMail.subject))
                stringBuilder.Append("Subject: \r\n");
            else
                stringBuilder.Append("Subject: =?UTF-8?B?" + Convert.ToBase64String(Encoding.UTF8.GetBytes(sendMail.subject)) + "?=\r\n");
            
            if (!string.IsNullOrEmpty(sendMail.toAddress))
                stringBuilder.Append("To: " + getContactEncode(sendMail.toAddress) + "\r\n");
            if (!string.IsNullOrEmpty(sendMail.ccAddresses))
                stringBuilder.Append("Cc: " + getContactEncode(sendMail.ccAddresses) + "\r\n");
            if (!string.IsNullOrEmpty(sendMail.bccAddresses))
                stringBuilder.Append("Bcc: " + getContactEncode(sendMail.bccAddresses) + "\r\n");
            stringBuilder.Append("MIME-Version: 1.0\r\n");

            if (sendMail.attachmentCount == 0)
            {
                if(sendMail.isHtml == 1)
                    stringBuilder.Append("Content-Type: text/html;charset=UTF-8\r\n");
                else
                    stringBuilder.Append("Content-Type: text/plain;charset=UTF-8\r\n");
                stringBuilder.Append("Content-Transfer-Encoding: base64\r\n");
                stringBuilder.Append("\r\n");
                if (data != null)
                    stringBuilder.Append(Convert.ToBase64String(data));
                stringBuilder.Append("\r\n.\r\n");
            }
            else
            {
                stringBuilder.Append("Content-Type: multipart/mixed; boundary=" + "\"676.1211442879.00000000@pushmail-server\"" + "\r\n");
                stringBuilder.Append("Content-Transfer-Encoding: base64\r\n");
                stringBuilder.Append("\r\n");

                ////
                stringBuilder.Append("--676.1211442879.00000000@pushmail-server\r\n");
                if (sendMail.isHtml == 1)
                    stringBuilder.Append("Content-Type: text/html;charset=UTF-8\r\n");
                else
                    stringBuilder.Append("Content-Type: text/plain;charset=UTF-8\r\n");

                if (strMailContent == null)
                    stringBuilder.Append("Content-size:0" + "\r\n");
                else
                    stringBuilder.Append("Content-size:" + strMailContent.Length.ToString() + "\r\n");
                
                stringBuilder.Append("Content-Transfer-Encoding: base64\r\n");
                stringBuilder.Append("\r\n");
                if (data != null)
                    stringBuilder.Append(Convert.ToBase64String(data));

                stringBuilder.Append("\r\n\r\n");

                //add attachment data
                PeDataModel peDataModel = PEDBManager.Instance().peDataModel;
                List<Attachment> attList = peDataModel.GetEmailAttachmentsInfo(sendMail.emailId);
                for (int i = 0; i < sendMail.attachmentCount; i++)
                {
                    Attachment att = attList[i];
                    string localFile = IsolateStorageUtil.GetAttachmentLocalFile(att);
                    byte[] attRawData = IsolateStorageUtil.ReadRawDataFile(localFile);

                    if (attRawData == null) continue;

                    stringBuilder.Append("--676.1211442879.00000000@pushmail-server\r\n");
                    stringBuilder.Append("Content-Type: application/octet-stream\r\n");
                    stringBuilder.Append("Content-Transfer-Encoding: base64\r\n");
                    int nPos = att.originalFilename.LastIndexOf("\\");
                    string attFileName = att.originalFilename.Substring(nPos + 1);

                    stringBuilder.Append("Content-Disposition: attachment; filename=\"=?UTF-8?B?" + Convert.ToBase64String(Encoding.UTF8.GetBytes(attFileName)) + "=?=\"\r\n");
                    stringBuilder.Append("Content-ID: " + (i + 1).ToString() + "\r\n\r\n");

                    stringBuilder.Append(Convert.ToBase64String(attRawData));
                    stringBuilder.Append("\r\n\r\n");
                    
                }

                stringBuilder.Append("--676.1211442879.00000000@pushmail-server--\r\n");
                stringBuilder.Append("\r\n.\r\n");
            }

            mimeData = stringBuilder.ToString();

            return mimeData;
        }

        private void SendRCPTTO()
        {
            if (_stringSendTo.Count > 0)
            {
                DefaultLogger.Log.LogDebug("SendRCPTTO " + _stringSendTo[0] + "\r\n");

                string sendStr = _stringSendTo[0];
                _stringSendTo.RemoveAt(0);

                SendData("RCPT TO:" + "<" + sendStr + ">\r\n");
                
            }
            else
            {
                if (sendMail.EFWDflag == 0)
                {
                    _sendState = CMSmtpCommandState.CMSmtp_DATA;
                    SendData("DATA\r\n");
                }
                else
                {
                    _sendState = CMSmtpCommandState.CMSmtp_EFWD;
                    //int nState = (int)(CMSmtpState.CMSmtp_SendEFWD);
                    //ChangeCMPopSmtpState(nState);
                    EFWDMail(sendMail);
                }
            }
        }

        override public int ProcessSendSucess()
        {
            //have more data to send?
            DefaultLogger.Log.LogDebug("ProcessSendSucess: have more data to send?" + "\r\n");
            //
            bool bAllDataComplete = true;
            if (bAllDataComplete)
            {
                _sendState = CMSmtpCommandState.CMSmtp_MAILEND;
                ReceiveData();
            }
            else
            {
                //send remained data

            }
            return 0;
        }

        private void SendMailData()
        {
            SendData(getMimedata(sendMail));
        }

        private bool EFWDMail(Email mail)
        {
            PeDataModel peDataModel = PEDBManager.Instance().peDataModel;
            string strParentUID = peDataModel.GetMailUID(mail.parentMailId);
            if (string.IsNullOrEmpty(strParentUID))
            {
                _sendState = CMSmtpCommandState.CMSmtp_DATA;
                SendData("DATA\r\n");
                return true;
            }
            else
            {
                //string strMime = getMimedata(mail);
                string strData = "EFWD " + strParentUID + "\r\n";
                //strData += getMimedata(mail);
                SendData(strData);
                return true;
            }
        }

        override public int ParseData(String inString)
        {
            if (inString == null)
                return 0;

            if (_stringRawDatabuf == null)
                _stringRawDatabuf = String.Copy(inString);
            else
                _stringRawDatabuf += inString;

            if (!_stringRawDatabuf.EndsWith("\r\n"))
            {
                ReceiveData();
            }

            switch (_sendState)
            {
                //0-waiting for response of MAIL FROM 
                case CMSmtpCommandState.CMSmtp_FROM:
                    {
                        if (_stringRawDatabuf.ToUpper().StartsWith("250 MAIL OK") == true)
                        {
                            _sendState = CMSmtpCommandState.CMSmtp_RCPTTO;
                            _stringRawDatabuf = null;

                            //send command: RCPT TO
                            SendRCPTTO();
                        }
                        else
                        {
                            ProcessSendFailed();
                            PostServerRetError((int)SockMessageType.SendFail);
                            //error
                            DefaultLogger.Log.LogDebug("Error response of MAIL FROM " + _stringRawDatabuf + "\r\n");
                        }
                    }
                    break;

                case CMSmtpCommandState.CMSmtp_RCPTTO://waiting for response of RCPT TO
                    {
                        if (_stringRawDatabuf.ToUpper().StartsWith("250 RCPT OK") == true)
                        {
                            _sendState = CMSmtpCommandState.CMSmtp_RCPTTO;
                            _stringRawDatabuf = null;

                            //send command: RCPT TO
                            SendRCPTTO();
                        }
                        else
                        {
                            ProcessSendFailed();
                            PostServerRetError((int)SockMessageType.SendFail);
                            //error
                            DefaultLogger.Log.LogDebug("Error response of RCPT TO " + _stringRawDatabuf + "\r\n");
                        }
                    }
                    break;

                case CMSmtpCommandState.CMSmtp_EFWD:
                case CMSmtpCommandState.CMSmtp_DATA://waiting for response of DATA
                    {
                        if (_stringRawDatabuf.ToUpper().StartsWith("354") == true)
                        {
                            int nState = (int)(CMSmtpState.CMSmtp_SendDATA);
                            ChangeCMPopSmtpState(nState);

                            _stringRawDatabuf = null;
                            SendMailData();
                        }
                        else if (_stringRawDatabuf.ToUpper().StartsWith("501") == true)
                        {

                            ProcessSendFailed();
                            string strErrPrefix = sendMail.subject + ": ";
                            PEDBManager.Instance().peDataModel.DeleteMail(sendMail);
                            PostServerRetError(501, strErrPrefix);
                        }
                        else
                        {
                            ProcessSendFailed();
                            //error
                            DefaultLogger.Log.LogDebug(_stringRawDatabuf);
                        }
                    }
                    break;


                case CMSmtpCommandState.CMSmtp_MAILEND://waiting for 250 message accepted
                    {
                        if (_stringRawDatabuf.ToUpper().StartsWith("250 MESSAGE ACCEPTED") == true)
                        {
                            PEDBManager.Instance().peDataModel.MoveToFolder(sendMail, FolderType.sentBoxType);
                            if (sendMail != null)
                            {
                                sendMail.sendResult = mail_sent;
                                PEDBManager.Instance().peDataModel.UpdateMail(sendMail);
                            }
                            int nState = (int)(CMSmtpState.CMSmtp_SendOneMail);
                            ChangeCMPopSmtpState(nState);
                            _stringRawDatabuf = null;

                            if ((!getSendMail()) || (sendMail == null))   //have no more mails to send
                            {
                                nState = (int)(CMSmtpState.CMSmtp_Quit);
                                ChangeCMPopSmtpState(nState);
                            }
                            else
                                sendOneMail();
                        }
                        else
                        {
                            ProcessSendFailed();
                            PostServerRetError((int)SockMessageType.SendFail);
                            //error
                            DefaultLogger.Log.LogDebug(_stringRawDatabuf);
                        }
                    }
                    break;

                default:
                    break;
            }

            return 0;
        }

        public override int ProcessSendFailed()
        {
            if (sendMail != null)
            {
                sendMail.sendResult = mail_sentfailed;
                PEDBManager.Instance().peDataModel.UpdateMail(sendMail);
            }           
            return base.ProcessSendFailed();
        }

    }
}
